/*
 * serial.cpp
 *
 *  Created on: Jan 16, 2014
 *      Author: yosef
 */

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <asm/termbits.h>
#include <sys/ioctl.h>
#include <timeout.h>
#include "serial.h"



// Clean serial resource
void Serial::Clean(const char *Device)
{
	unsigned char i;

    // Assignment path
    strncpy(path, (const char *)Device, 24);
    for (i = 0; i < 24; i++)
    {
    	if((path[i] = '/') || (path[i] == '\\'))
    			path[i] = '_';
    }
    path[i] = '\0';

	//Destroy semaphore
    sem_close(mutex);
    sem_unlink(path);
}

// Open a device
int Serial::Open(const char *Device, const unsigned long int Bauds, const char DataBit,
		const char *Parity, const char StopBit, bool HwCtrl)
{
	const char par_none[] = "none";
	const char par_odd[] = "odd";
	const char par_even[] = "even";
	const char par_mark[] = "mark";
	const char par_space[] = "space";
	unsigned char i;
    struct termios2 options;

    // Check Device sanity
    if (Device == NULL) return -1;
    if (Parity == NULL) return -1;

    // Assignment path
    strncpy(path, (const char *)Device, 24);
    for (i = 0; i < 24; i++)
    {
    	if((path[i] = '/') || (path[i] == '\\'))
    			path[i] = '_';
    }
    path[i] = '\0';

    // Create and initialize semaphore
    mutex = sem_open(path, O_CREAT |O_EXCL, 0644, 1);
    if(mutex == SEM_FAILED) {
    	return -5;
    }

    // Open device
    fd = open(Device, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1) {
    	//Destroy semaphore
        sem_close(mutex);
        sem_unlink(path);
    	return -2;
    }

    // Set nonblocking mode
    fcntl(fd, F_SETFL, FNDELAY);
    // Get the current options of the port
    if(ioctl(fd, TCGETS2, &options) < 0) {
        close (fd);
        sem_close(mutex);
        sem_unlink(path);
        return -3;
    }

    // Clear all options
    bzero(&options, sizeof(options));

    // Set baud rate
    options.c_cflag &= ~CBAUD;
    options.c_cflag |= BOTHER;
    options.c_ispeed = Bauds;
    options.c_ospeed = Bauds;

    // Configure the device
    options.c_cflag |= (CLOCAL | CREAD);
    // Data length
    switch(DataBit) {
    	case 5:
    		options.c_cflag &= ~CSIZE;
    		options.c_cflag |= CS5;
    	break;

    	case 6:
    		options.c_cflag &= ~CSIZE;
    		options.c_cflag |= CS6;
    	break;

    	case 7:
    		options.c_cflag &= ~CSIZE;
    		options.c_cflag |= CS7;
    	break;

    	case 8:
    		options.c_cflag &= ~CSIZE;
    		options.c_cflag |= CS8;
    	break;

    	default:
    	    close (fd);
    	    sem_close(mutex);
    	    sem_unlink(path);
    		return -4;
    	break;
    }

    // Stop bit
    if(StopBit == 1) {
    	options.c_cflag &= ~CSTOPB;
    }
    else if(StopBit == 2) {
    	options.c_cflag |= CSTOPB;
    }
    else {
    	options.c_cflag &= ~CSTOPB;
    }

    // Parity
    if (!strncmp(Parity, par_none, 10)) {
    	options.c_cflag &= ~PARENB;
    }
    else if(!strncmp(Parity, par_odd, 10)) {
    	options.c_cflag |= PARENB;
    	options.c_cflag |= PARODD;
    }
    else if (!strncmp(Parity, par_even, 10)) {
    	options.c_cflag |= PARENB;
    	options.c_cflag &= ~PARODD;
    }
    // Mark parity is simulated by using 2 stop bits
    else if (!strncmp(Parity, par_mark, 10)) {
    	options.c_cflag &= ~PARENB;
    	options.c_cflag |= CSTOPB;
    }
    else if (!strncmp(Parity, par_space, 10)) {
    	options.c_cflag &= ~PARENB;
    }
    else {
        close (fd);
        sem_close(mutex);
        sem_unlink(path);
    	return -4;
    }

    // Hardware control
    if (HwCtrl == true) {
    	options.c_cflag |= CRTSCTS;
    }
    else {
    	options.c_cflag &= ~CRTSCTS;
    }

    // Input option
    options.c_iflag |= (IGNPAR | IGNBRK );
    // Timer unused
    options.c_cc[VTIME] = 0;
    options.c_cc[VMIN] = 0;
    // Activate the settings
    if(ioctl(fd, TCSETS2, &options) < 0) {
        close (fd);
        sem_close(mutex);
        sem_unlink(path);

        return -3;
    }

    return 0;
}


// Close a device
void Serial::Close()
{
    close (fd);
    sem_close(mutex);
    sem_unlink(path);
}


// Write a char
int Serial::WriteChar(const char Byte)
{
	int retval = 0;

	sem_wait(mutex);

    if (write(fd, &Byte, 1) != 1)
    	retval = -1;

    sem_post(mutex);
    return retval;
}


// Write a string
int Serial::WriteString(const char *String)
{
    int Lenght, retval = 0;

    Lenght = strlen(String);

    sem_wait(mutex);
    if (write(fd, String, Lenght) != Lenght)
    	retval = -1;

    sem_post(mutex);
    return retval;
}


// Write an array of byte
int Serial::Write(const void *Buffer, const unsigned int NbBytes)
{
	int retval = 0;

	sem_wait(mutex);
    if (write (fd, Buffer, NbBytes) != (ssize_t)NbBytes)
    	retval = -1;

    sem_post(mutex);
    return retval;
}


// Read a char (with timeout)
int Serial::ReadChar(char *pByte, unsigned int TimeOut_ms)
{
    TimeOut Timer;

    // Sanity
    if (pByte == NULL)
    	return -1;

    Timer.ResetTimer();
    sem_wait(mutex);
    while (Timer.ElapsedTime_ms() < TimeOut_ms || TimeOut_ms == 0)
    {
    	if (read(fd, pByte, 1) == 1) {
    		sem_post(mutex);
    		return 1;
    	}
    }

    sem_post(mutex);
    return 0;
}


// Read string with timeout
int Serial::ReadString(char *String, char FinalChar, unsigned int MaxNbBytes, unsigned int TimeOut_ms)
{
    unsigned int NbBytes = 0;

    // Sanity
    if (String == NULL)
    	return -1;

    if (TimeOut_ms == 0)
        return ReadStringNoTimeOut(String, FinalChar, MaxNbBytes);

    while (NbBytes < MaxNbBytes)
    {
    	if (ReadChar(&String[NbBytes], TimeOut_ms) < 1)
    	{
    		// Timeout reached
    		String[NbBytes] = '\0';
    		return NbBytes;
    	}

    	// Final character
    	if (String[NbBytes] == FinalChar)
    	{
    		String[++NbBytes] = '\0';
    		return NbBytes;
    	}

    	NbBytes++;
    }

    return NbBytes;
}


// Read a string (without timeout)
int Serial::ReadStringNoTimeOut(char *String, char FinalChar, unsigned int MaxNbBytes)
{
    unsigned int NbBytes = 0;

    // Sanity
    if (String == NULL)
    	return -1;

    while (NbBytes < MaxNbBytes)
    {
        if (ReadChar(&String[NbBytes], 0) < 0)
        	return -2;

        if (String[NbBytes] == FinalChar)
        {
        	String[++NbBytes] = '\0';
        	return NbBytes;
        }
        NbBytes++;
    }

    return NbBytes;
}

// Read an array of byte (with timeout)
int Serial::Read(void *Buffer, unsigned int MaxNbBytes, unsigned int TimeOut_ms)
{
    TimeOut Timer;
    int retval;
    unsigned int NbByteRead = 0;
    unsigned char *Ptr;

    // Sanity
    if (Buffer == NULL)
    	return -1;

    sem_wait(mutex);
    Timer.ResetTimer();
    while (Timer.ElapsedTime_ms() < TimeOut_ms || TimeOut_ms == 0)
    {
        Ptr = (unsigned char *)Buffer + NbByteRead;
        retval = read(fd, (void *)Ptr, MaxNbBytes - NbByteRead);
        if (retval > 0) {
        	Timer.ResetTimer();
        	NbByteRead += retval;
            if (NbByteRead >= MaxNbBytes) {
            	sem_post(mutex);
            	return NbByteRead;
            }
        }
        else if (retval < 0) {
        	sem_post(mutex);
        	return -2;
        }
    }

    sem_post(mutex);
    return NbByteRead;
}


// Return the number of bytes in the received buffer
int Serial::Peek(void)
{
    int Nbytes=0;
    ioctl(fd, FIONREAD, &Nbytes);

    return Nbytes;
}



